package com.googlecode.project4uz.encoder;

import java.beans.Expression;
import java.beans.Statement;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.Map;

import org.openrdf.rio.RDFFormat;

import com.googlecode.project4uz.encoder.RioWriter.Reference;

/**
 * Odczytuje publiczne pola z obiektow. Pomija pola oznaczone modyfikatorami:
 * final, static oraz transient. Potrafi odczytac prywatne pole serialVersionUID.
 * @author Daniel Kur
 * @see http://code.google.com/p/project4uz/wiki/Mechanizm_refleksji
 */
public class SerializerPublic extends AbstractSerializer {
	/**
	 * Tworzy obiekt odczytujacy publiczne pola z innych obiektow.
	 * @param output - strumien wyjsciowy.
	 * @param format - format zapisu RDF.
	 */
	SerializerPublic(final RDFFormat format, final OutputStream output) {
		super(format, output);
	}
	
	@Override
	void readObject(final Object object) {
		if(object == null)
			LOG.warning("Nie moge serializowac: obiekt jest pusta referencja!");

		java.lang.Class<?> c = object.getClass();
		Field[] publicFields = c.getFields();
		RioWriter.Reference cls = null;

		String id;
		if((id = getIdentificator(object.hashCode())) != null || object == owner) {
			writer.writePointer(c.getName(), id);
			return;
		} else {
			cls = writer.writeClass(c.getName());
			readSerialVersion(object, cls);
			reference.put(object.hashCode(), cls.getID());
		}
		
		for(Field field : publicFields) {
			if(Modifier.isFinal(field.getModifiers()) || Modifier.isStatic(field.getModifiers()) ||
				Modifier.isTransient(field.getModifiers())) continue;

			java.lang.Class<?> type = field.getType();
			String name = field.getName();			
			Object value = readPrimitive(object, field);
			
			if(value == null) {
				cls.writeNull(name, type.getSimpleName());
			} else if(isSimpleType(value.getClass())) {
				cls.writePrimitive(name, type.getSimpleName(), value.toString());
			} else if(type.isArray()) {
				RioWriter.Reference.Array tab = cls.writeArray(name, getComponentType(value), Array.getLength(value), getDimension(value));
				this.readArray(value, tab);
			} else if(value instanceof java.util.Collection<?>) {
				ParameterizedType generic = (ParameterizedType)field.getGenericType();
				RioWriter.Reference.Collection col = cls.writeCollection(name, type.getName(), generic.getActualTypeArguments()[0].toString().substring(6));
				this.readCollection((Collection<?>) value, col);
			} else if(value instanceof java.util.Map<?, ?>) {
				ParameterizedType generic = (ParameterizedType)field.getGenericType();
				RioWriter.Reference.Map map = cls.writeMap(name, type.getName(), generic.getActualTypeArguments()[0].toString().substring(6), generic.getActualTypeArguments()[1].toString().substring(6));
				this.readMap((Map<?, ?>) value, map);
			} else {
				RioWriter.Reference ref = null;
				if((id = getIdentificator(value.hashCode())) != null) {
					cls.writePointer(name, id);
				} else {
					ref = cls.writeReference(name, value.getClass().getName());
					this.readReference(value, ref);
					reference.put(value.hashCode(), ref.getID());
				}
			}
		}
	}

	@Override
	void readReference(final Object object, final Reference ref) {
		java.lang.Class<?> c = object.getClass();
		Field[] publicFields = c.getFields();

		for(Field field : publicFields) {
			if(Modifier.isFinal(field.getModifiers()) || Modifier.isStatic(field.getModifiers()) ||
					Modifier.isTransient(field.getModifiers())) continue;
			
			java.lang.Class<?> type = field.getType();
			String name = field.getName();			
			Object value = readPrimitive(object, field);
			
			if(value == null) {
				ref.writeNull(name, type.getSimpleName());
			} else if(isSimpleType(value.getClass())) {
				ref.writePrimitive(name, type.getSimpleName(), value.toString());
			} else if(type.isArray()) {
				RioWriter.Reference.Array tab = ref.writeArray(name, getComponentType(value), Array.getLength(value), getDimension(value));
				this.readArray(value, tab);
			} else if(value instanceof java.util.Collection<?>) {
				ParameterizedType generic = (ParameterizedType)field.getGenericType();
				RioWriter.Reference.Collection col = ref.writeCollection(name, type.getName(), generic.getActualTypeArguments()[0].toString().substring(6));
				this.readCollection((Collection<?>) value, col);
			} else if(value instanceof java.util.Map<?, ?>) {
				ParameterizedType generic = (ParameterizedType)field.getGenericType();
				RioWriter.Reference.Map map = ref.writeMap(name, type.getName(), generic.getActualTypeArguments()[0].toString().substring(6), generic.getActualTypeArguments()[1].toString().substring(6));
				this.readMap((Map<?, ?>) value, map);
			} else {			
				RioWriter.Reference r = ref.writeReference(name, value.getClass().getName());
				this.readReference(value, r);
			}
		}
	}
	
	@Override
	void readExpression(final Expression exp) {
		if(exp == null)
			LOG.warning("Nie moge dodac wyrazenia: obiekt jest pusta referencja!");
		
		String id;
		if((id = getIdentificator(exp.getTarget().hashCode())) == null)
		{
			Reference cls = writer.writeClass(exp.getTarget().getClass().getName());
			reference.put(exp.getTarget().hashCode(), id = cls.getID());
		}

		try {
			Object value = exp.getValue();
			if(!value.equals(new Object()))
				writer.addExpression(id, exp.getTarget().getClass().getField(exp.getMethodName()).getType().getSimpleName(),
									  exp.getMethodName(), value+"");
		} catch(Exception e) {
			LOG.warning("Nie moge dodac wyrazenia: " + exp.getMethodName());
		}
	}

	@Override
	void readStatement(final Statement stmt) {
		if(stmt == null)
			LOG.warning("Nie moge dodac deklaracji: obiekt jest pusta referencja!");

		try {
			String id;
			if((id = getIdentificator(stmt.getTarget().hashCode())) == null) {
				Reference cls = writer.writeClass(stmt.getTarget().getClass().getName());
				reference.put(stmt.getTarget().hashCode(), cls.getID());
				Object r = stmt.getTarget().getClass().getField(stmt.getMethodName()).get(stmt.getTarget());
				Reference ref = cls.writeReference(stmt.getMethodName(), r.getClass().getName());
				readReference(r, ref);
			} else {
				Reference cls = writer.new Reference(id.toCharArray());
				Object r = stmt.getTarget().getClass().getField(stmt.getMethodName()).get(stmt.getTarget());
				Reference ref = cls.writeReference(stmt.getMethodName(), r.getClass().getName());
				readReference(r, ref);
			}
		} catch(Exception e) {
			LOG.warning("Nie moge dodac deklaracji: " + stmt.getMethodName());
		}
	}
}
